package com.duoduo.commonn.redis.factory;

import com.duoduo.common.api.constants.Constant;
import com.duoduo.common.api.constants.LogConstant;
import com.duoduo.common.api.exception.lock.AddLockException;
import com.duoduo.common.api.exception.lock.ReleaseLockException;
import com.duoduo.common.api.exception.lock.UnrerentLockException;
import com.duoduo.common.api.help.DynamicProxyInnerUtil;
import com.duoduo.common.api.help.ListInnerUtil;
import com.duoduo.common.api.help.SpringContextInnerUtil;
import com.duoduo.common.api.myinterface.LockListOperator;
import com.duoduo.common.api.myinterface.LockOperator;
import com.duoduo.common.api.myinterface.tracer.RedisTracer;
import com.duoduo.common.api.spi.extension.ExtensionLoader;
import com.duoduo.commonn.redis.LogThreadLocal;
import com.duoduo.commonn.redis.handle.TraceHandler;
import com.duoduo.commonn.redis.myinterface.RedisOperator;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.List;
import java.util.UUID;

public class RedisLockListFactory implements LockListOperator {
	
	private static final Logger logger = LoggerFactory.getLogger(RedisLockListFactory.class);
	private LockOperator lockOperator = null;
	private RedisOperator redisOperator = null;
	private static final String GLOBAL_LOCK = "ll-global";
	private static ThreadLocal<RequestIdData> requestIdThreadLocal = new ThreadLocal<RequestIdData>() {
		public RequestIdData initialValue() {
			return new RequestIdData();
		}
	};
	
	public RedisLockListFactory(){
		try{
			//分布式锁的具体实现工具，默认是redis
			String cacheType = SpringContextInnerUtil.get(Constant.LOCK_TYPE);
			lockOperator = ExtensionLoader.getExtensionLoader(LockOperator.class).getExtension(cacheType);
		}catch(Exception e){
			logger.error(LogConstant.DISTRIBUTE_LOCK + " init lockList error", e);
		}
		
		try{
			//分布式锁默认使用redis实现，redis默认使用jedis操作
			String redisImplType = SpringContextInnerUtil.get(Constant.REDIS_IMPL);
			logger.info(LogConstant.REDIS_CACHE + " impl type: {}", redisImplType);
			// 如果有链路跟踪
	     	if(SpringContextInnerUtil.containsBean(Constant.TRACER_BEANNAME)){
				RedisOperator proxy = ExtensionLoader.getExtensionLoader(RedisOperator.class).getExtension(redisImplType);
				RedisTracer tracer = ExtensionLoader.getExtensionLoader(RedisTracer.class).getExtension(Constant.TRACER_REDIS_CLIENT_NAME);
				redisOperator = DynamicProxyInnerUtil.getProxy(proxy, new TraceHandler<RedisOperator>(tracer));
	     	} else {
	     		redisOperator = ExtensionLoader.getExtensionLoader(RedisOperator.class).getExtension(redisImplType);
	     	}
		}catch(Exception e){
			logger.error(LogConstant.DISTRIBUTE_LOCK + " init lockList redisOperator error", e);
		}
	}

	@Override
	public boolean tryGetDistributedLock(String prefixName, String appName, List<String> lockKeys, String _requestId, int timeout, boolean timeOutThenExecBussiness) throws AddLockException {
		// 为了保证整体操作的原子性，可以使用lur脚本，也可以使用全局锁，这里采用全局锁的实现方式
		// 1、在指定超时时间内加全局锁，如果加锁失败，就认为加锁失败了；加锁成功，则可以进行后面的操作
		String requestId = null;
		// 如果threadlocal里有requestId，则直接使用，否则uuid生成新的
		requestId = requestIdThreadLocal.get().requestId;
		if(requestId != null){
			if(!LogThreadLocal.restrainLockLog.get()) {
				logger.info(LogConstant.REDIS_LOCK + " old requestId, appName: {}, lockKey: {}, requestId: {}, timeout: {}", appName, GLOBAL_LOCK, requestId, timeout);
			}
		}else{
			if(StringUtils.isNotEmpty(_requestId)){
				requestId = _requestId;
				if(!LogThreadLocal.restrainLockLog.get()) {
					logger.info(LogConstant.REDIS_LOCK + " param requestId, appName: {}, lockKey: {}, requestId: {}, timeout: {}", appName, GLOBAL_LOCK, requestId, timeout);
				}
			}else{
				requestId = UUID.randomUUID().toString();
				if(!LogThreadLocal.restrainLockLog.get()) {
					logger.info(LogConstant.REDIS_LOCK + " new requestId, appName: {}, lockKey: {}, requestId: {}, timeout: {}", appName, GLOBAL_LOCK, requestId, timeout);
				}
			}
		}
		long start = System.currentTimeMillis();
		
		try {
			while(true) {
				// 尝试加全局锁
				if(lockOperator.tryGetDistributedLock(prefixName, appName, GLOBAL_LOCK, requestId, 0, timeOutThenExecBussiness)) {
					// 加全局锁成功
					// 是否有资格加业务锁
					boolean getLock = true;
					String[] lockKeyArr = ListInnerUtil.listToSArr(lockKeys);
					// 得到所有业务锁在redis里对应的数据
					List<String> lockRequestIds = redisOperator.mget(prefixName, appName, lockKeyArr);
					if(ListInnerUtil.isEmpty(lockRequestIds)) {
						// 一般不会进入这个分支，就算拿不到数据也会返回[null,null]这样
						getLock = false;
						try {
							// 释放全局锁
							lockOperator.releaseDistributedLock(prefixName, appName, GLOBAL_LOCK, true, true);
						}catch (Exception e) {
							logger.error(LogConstant.DISTRIBUTE_LOCK + " lockList release global lock fail", e);
						}
					}else {
						for (String lockRequestId : lockRequestIds) {
							if(lockRequestId != null && !lockRequestId.equals(requestId)) {
								// 说明业务锁里至少有一个被别的线程加了锁，跳出for循环并标识getLock为false
								getLock = false;
								try {
									// 释放全局锁
									lockOperator.releaseDistributedLock(prefixName, appName, GLOBAL_LOCK, true, true);
								}catch (Exception e) {
									logger.error(LogConstant.DISTRIBUTE_LOCK + " lockList release global lock fail", e);
								}
								break;
							}
						}
					}
					
					if(getLock) {
						for (String lockKey : lockKeyArr) {
							boolean b = lockOperator.tryGetDistributedLock(prefixName, appName, lockKey, requestId, 0, timeOutThenExecBussiness);
							if(!b) {
								// 理论上走到这里说明lockKeys里的key要么是没有加过锁的，要么是自己重入的，不应该会返回false，如果返回false这里要记录一个error
								logger.error(LogConstant.DISTRIBUTE_LOCK + " lockList add lock fail, current key: " + lockKey + ", lockKeys: " + lockKeyArr);
							}
						}
						// 走到这里说明加锁成功或重入成功
						requestIdThreadLocal.get().requestId = requestId;
						requestIdThreadLocal.get().addCount();
						return true;
					}
				}
				// 走到这里说明没有拿到全局锁，或者拿到了但是业务锁被占用
				if(timeout == -1){
	    			try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
						// no-op
					}
	    		}else if((System.currentTimeMillis() - start) < timeout * 1000){
		    		// 超时时间还没到，尝试获取锁
	    			try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
						// no-op
					}
		    	}else{
		    		// 超时后，还是没得到全局锁或者业务锁，加锁失败
		    		return false;
		    	}
			}
		}finally {
			// 释放全局锁
			try {
				// 释放全局锁
				lockOperator.releaseDistributedLock(prefixName, appName, GLOBAL_LOCK, true, true);
			}catch (Exception e) {
				logger.error(LogConstant.DISTRIBUTE_LOCK + " lockList release global lock fail", e);
			}
		}
	}

	@Override
	public boolean releaseDistributedLock(String prefixName, String appName, List<String> lockKeys, Boolean release) throws ReleaseLockException, UnrerentLockException {
		try {
			for (String lockKey : lockKeys) {
				try {
					// 释放业务锁
					lockOperator.releaseDistributedLock(prefixName, appName, lockKey, true, false);
				}catch (Exception e) {
					logger.error(LogConstant.DISTRIBUTE_LOCK + " lockList release lock fail, current key: " + lockKey, e);
				}
			}
		}finally {
			// 重入减到0后，可以清空ThreadLocal里的requestId
			requestIdThreadLocal.get().subCount();
		}
        return true;
	}
	
	static class RequestIdData implements Serializable {
		private static final long serialVersionUID = 4649574026224159715L;
		public String requestId;
		public int count = 0;
		
		public RequestIdData() {
			super();
		}

		public void subCount() {
			count--;
			if(count == 0) {
				// 减到0，彻底释放，清空requestId
				requestId = null;
			}
		}

		public void addCount() {
			count++;
		}
	}

}
